home *** CD-ROM | disk | FTP | other *** search
/ Shareware Grab Bag / Shareware Grab Bag.iso / 011 / tsrsrc22.arc / MAPMEM.PAS < prev    next >
Pascal/Delphi Source File  |  1987-03-05  |  30KB  |  930 lines

  1. {**************************************************************************
  2. *   Maps system memory blocks for MS/PCDOS 2.0 and higher.                *
  3. *   Also maps expanded memory allocation blocks                           *
  4. *   Copyright (c) 1986 Kim Kokkonen, TurboPower Software.                 *
  5. *   Released to the public domain for personal, non-commercial use only.  *
  6. ***************************************************************************
  7. *   version 1.0 1/2/86                                                    *
  8. *   version 1.1 1/10/86                                                   *
  9. *     running under DOS 2.X, where block owner names are unknown          *
  10. *   version 1.2 1/22/86                                                   *
  11. *     a bug in parsing the owner name of the block                        *
  12. *     a quirk in the way that the DOS PRINT buffer installs itself        *
  13. *     minor cosmetic changes                                              *
  14. *   version 1.3 2/6/86                                                    *
  15. *     smarter filtering for processes that deallocate their environment   *
  16. *   version 1.4 2/23/86                                                   *
  17. *     add a map of Expanded memory (EMS) as well                          *
  18. *   version 1.5 2/26/86                                                   *
  19. *     change format of last memory block                                  *
  20. *     change to more reliable scheme of finding first block               *
  21. *       (thanks to Chris Dunford for pointing out a useful                *
  22. *        undocumented DOS function).                                      *
  23. *     support environment lengths up to 32K                               *
  24. *   version 1.6 3/8/86                                                    *
  25. *     support "verbose" output mode                                       *
  26. *       display open file handles                                         *
  27. *       show command line of each block                                   *
  28. *   version 1.7 3/24/86                                                   *
  29. *     work around Turbo 3.00B bug with Delete procedure and length 255    *
  30. *     filter out command lines of programs which relocate over their      *
  31. *       command line at PSP:$80.                                          *
  32. *     fix treatment of handle counts from PSP                             *
  33. *     add display of number of memory blocks per PSP to verbose mode      *
  34. *     accept V, -V, or /V for the verbose switch                          *
  35. *   version 1.8 4/20/86                                                   *
  36. *     change verbose mode to show each block individually                 *
  37. *   version 1.9 5/22/86                                                   *
  38. *     synchronize with RELEASE                                            *
  39. *   version 2.0 6/17/86                                                   *
  40. *     synchronize with RELEASE                                            *
  41. *   version 2.1 7/18/86                                                   *
  42. *     wrap long vector lists                                              *
  43. *   version 2.2 3/4/87                                                    *
  44. *     adds support for WATCH files                                        *
  45. ***************************************************************************
  46. *   telephone: 408-438-8608, CompuServe: 72457,2131.                      *
  47. *   requires Turbo version 3 to compile.                                  *
  48. *   Compile with mAx dynamic memory = FFFF.                               *
  49. ***************************************************************************}
  50.  
  51. {$P128}
  52.  
  53. program MapMem;
  54.   {-look at the system memory map using DOS memory control blocks}
  55. const
  56.   Version = '2.2';
  57.   MaxBlocks = 100;            {max number of DOS memory blocks checked}
  58.   MaxVector = $FF;            {highest interrupt vector checked for trapping}
  59. type
  60.   Pathname = string[64];
  61.   AllStrings = string[255];
  62.  
  63.   BlockType = 0..MaxBlocks;
  64.   Block =
  65.   record                      {store info about each memory block as it is found}
  66.     idbyte : Byte;
  67.     mcb : Integer;
  68.     psp : Integer;
  69.     len : Integer;
  70.     psplen : Integer;
  71.     env : Integer;
  72.     cnt : Integer;
  73.   end;
  74.  
  75.   ChainVector =
  76.   record                      {store info read from a watch file}
  77.     psp : Integer;
  78.     vecstr : AllStrings;
  79.   end;
  80.   ChainArray = array[BlockType] of ChainVector;
  81.  
  82.   BlockArray = array[BlockType] of Block;
  83.   registers =
  84.   record
  85.     case Integer of
  86.       1 : (ax, bx, cx, dx, bp, si, di, ds, es, flags : Integer);
  87.       2 : (al, ah, bl, bh, cl, ch, dl, dh : Byte);
  88.   end;
  89.  
  90. var
  91.   Blocks : BlockArray;
  92.   ChainedVecs : ChainArray;
  93.   BlockNum, NumChains : BlockType;
  94.   verbose, usewatch : Boolean;
  95.  
  96.   procedure Abort(msg : AllStrings);
  97.     {-halt in case of error}
  98.   begin
  99.     WriteLn(msg);
  100.     Halt(1);
  101.   end {Abort} ;
  102.  
  103.   function StUpcase(s : Pathname) : Pathname;
  104.     {-return the upper case of a string}
  105.   var
  106.     i : Byte;
  107.   begin
  108.     for i := 1 to Length(s) do s[i] := UpCase(s[i]);
  109.     StUpcase := s;
  110.   end {stupcase} ;
  111.  
  112.   procedure FindTheBlocks;
  113.     {-scan memory for the allocated memory blocks}
  114.   const
  115.     MidBlockID = $4D;         {byte DOS uses to identify part of MCB chain}
  116.     EndBlockID = $5A;         {byte DOS uses to identify last block of MCB chain}
  117.   var
  118.     mcbSeg : Integer;         {segment address of current MCB}
  119.     nextSeg : Integer;        {computed segment address for the next MCB}
  120.     gotFirst : Boolean;       {true after first MCB is found}
  121.     gotLast : Boolean;        {true after last MCB is found}
  122.     idbyte : Byte;            {byte that DOS uses to identify an MCB}
  123.  
  124.     function GetStartMCB : Integer;
  125.       {-return the first MCB segment}
  126.     var
  127.       reg : registers;
  128.     begin
  129.       reg.ah := $52;
  130.       MsDos(reg);
  131.       GetStartMCB := MemW[reg.es:(reg.bx-2)];
  132.     end {getstartmcb} ;
  133.  
  134.     procedure StoreTheBlock(var mcbSeg, nextSeg : Integer;
  135.                             var gotFirst, gotLast : Boolean);
  136.       {-store information regarding the memory block}
  137.     var
  138.       nextID : Byte;
  139.       pspAdd : Integer;       {segment address of the current PSP}
  140.       mcbLen : Integer;       {size of the current memory block in paragraphs}
  141.  
  142.     begin
  143.  
  144.       mcbLen := MemW[mcbSeg:3]; {size of the MCB in paragraphs}
  145.       nextSeg := Succ(mcbSeg+mcbLen); {where the next MCB should be}
  146.       pspAdd := MemW[mcbSeg:1]; {address of program segment prefix for MCB}
  147.       nextID := Mem[nextSeg:0];
  148.  
  149.       if gotLast or (nextID = EndBlockID) or (nextID = MidBlockID) then begin
  150.         BlockNum := Succ(BlockNum);
  151.         gotFirst := True;
  152.         with Blocks[BlockNum] do begin
  153.           idbyte := Mem[mcbSeg:0];
  154.           mcb := mcbSeg;
  155.           psp := pspAdd;
  156.           env := MemW[pspAdd:$2C];
  157.           len := mcbLen;
  158.           psplen := 0;
  159.           cnt := 1;
  160.         end;
  161.       end;
  162.  
  163.     end {storetheblock} ;
  164.  
  165.   begin
  166.  
  167.     {initialize}
  168.     mcbSeg := GetStartMCB;
  169.     gotFirst := False;
  170.     gotLast := False;
  171.     BlockNum := 0;
  172.  
  173.     {scan all memory until the last block is found}
  174.     repeat
  175.       idbyte := Mem[mcbSeg:0];
  176.       if idbyte = MidBlockID then begin
  177.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  178.         if gotFirst then mcbSeg := nextSeg else mcbSeg := Succ(mcbSeg);
  179.       end else if gotFirst and (idbyte = EndBlockID) then begin
  180.         gotLast := True;
  181.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  182.       end else
  183.         {start block was invalid}
  184.         Abort('corrupted allocation chain or program error');
  185.     until gotLast;
  186.  
  187.   end {findtheblocks} ;
  188.  
  189.   function Cardinal(i : Integer) : Real;
  190.     {-return an unsigned integer 0..65535}
  191.   begin
  192.     Cardinal := 256.0*Hi(i)+Lo(i);
  193.   end {cardinal} ;
  194.  
  195.   procedure StripNonAscii(var t : Pathname);
  196.     {-return an empty string if t contains any non-printable characters}
  197.   var
  198.     ipos : Byte;
  199.     goodname : Boolean;
  200.   begin
  201.     goodname := True;
  202.     for ipos := 1 to Length(t) do
  203.       if (t[ipos] <> #0) and ((t[ipos] < ' ') or (t[ipos] > '}')) then
  204.         goodname := False;
  205.     if not(goodname) then t := '';
  206.   end {stripnonascii} ;
  207.  
  208.   procedure ShowTheBlocks;
  209.     {-analyze and display the blocks found}
  210.   const
  211.     hookst : string[14] = 'hooked vectors';
  212.     chainst : string[15] = 'chained vectors';
  213.   type
  214.     HexString = string[4];
  215.     Address = record
  216.                 offset, segment : Integer;
  217.               end;
  218.     VectorType = 0..MaxVector;
  219.   var
  220.     st, cline : Pathname;
  221.     b : BlockType;
  222.     StLen, DOSv : Byte;
  223.     CommandPSP : Integer;
  224.     Vectors : array[VectorType] of Address absolute 0 : 0;
  225.     Vtable : array[VectorType] of Real;
  226.     SumNum : BlockType;
  227.     Sum : BlockArray;
  228.  
  229.     function Hex(i : Integer) : HexString;
  230.       {-return hex representation of integer}
  231.     const
  232.       hc : array[0..15] of Char = '0123456789ABCDEF';
  233.     var
  234.       l, h : Byte;
  235.     begin
  236.       l := Lo(i); h := Hi(i);
  237.       Hex := hc[h shr 4]+hc[h and $F]+hc[l shr 4]+hc[l and $F];
  238.     end {hex} ;
  239.  
  240.     function DOSversion : Byte;
  241.       {-return the major version number of DOS}
  242.     var
  243.       reg : registers;
  244.     begin
  245.       reg.ah := $30;
  246.       MsDos(reg);
  247.       DOSversion := reg.al;
  248.     end {dosversion} ;
  249.  
  250.     function Owner(startadd : Integer) : Pathname;
  251.       {-return the name of the owner program of an MCB}
  252.     type
  253.       chararray = array[0..32767] of Char;
  254.     var
  255.       e : ^chararray;
  256.       i : Integer;
  257.       t : Pathname;
  258.  
  259.       function LongPos(m : Pathname; var s : chararray) : Integer;
  260.         {-return the position number of m in s, or 0 if not found}
  261.       var
  262.         mc : Char;
  263.         ss : Pathname;
  264.         i, maxindex : Integer;
  265.         found : Boolean;
  266.       begin
  267.         i := 0;
  268.         maxindex := SizeOf(s)-Length(m);
  269.         ss[0] := m[0];
  270.         if Length(m) > 0 then begin
  271.           mc := m[1];
  272.           repeat
  273.             while (s[i] <> mc) and (i <= maxindex) do
  274.               i := Succ(i);
  275.             if s[i] = mc then begin
  276.               Move(s[i], ss[1], Length(m));
  277.               found := (ss = m);
  278.               if not(found) then i := Succ(i);
  279.             end;
  280.           until found or (i > maxindex);
  281.           if not(found) then i := 0;
  282.         end;
  283.         LongPos := i;
  284.       end {longpos} ;
  285.  
  286.       procedure StripPathname(var pname : Pathname);
  287.         {-remove leading drive or path name from the input}
  288.       var
  289.         spos, cpos, rpos : Byte;
  290.       begin
  291.         spos := Pos('\', pname);
  292.         cpos := Pos(':', pname);
  293.         if spos+cpos = 0 then Exit;
  294.         if spos <> 0 then begin
  295.           {find the last slash in the pathname}
  296.           rpos := Length(pname);
  297.           while (rpos > 0) and (pname[rpos] <> '\') do rpos := Pred(rpos);
  298.         end else
  299.           rpos := cpos;
  300.         Delete(pname, 1, rpos);
  301.       end {strippathname} ;
  302.  
  303.       procedure StripExtension(var pname : Pathname);
  304.         {-remove the file extension}
  305.       var
  306.         dotpos : Byte;
  307.       begin
  308.         dotpos := Pos('.', pname);
  309.         if dotpos <> 0 then
  310.           Delete(pname, dotpos, 64); {<255 needed for Turbo version 3.00B bug}
  311.       end {stripextension} ;
  312.  
  313.     begin
  314.       {point to the environment string}
  315.       e := Ptr(startadd, 0);
  316.  
  317.       {find end of the standard environment}
  318.       i := LongPos(#0#0, e^);
  319.       if i = 0 then begin
  320.         {something's wrong, exit gracefully}
  321.         Owner := '';
  322.         Exit;
  323.       end;
  324.  
  325.       {end of environment found, get the program name that follows it}
  326.       t := '';
  327.       i := i+4;               {skip over #0#0#args}
  328.       repeat
  329.         t := t+e^[i];
  330.         i := Succ(i);
  331.       until (Length(t) > 64) or (e^[i] = #0);
  332.  
  333.       StripNonAscii(t);
  334.       if t = '' then
  335.         Owner := 'N/A'
  336.       else begin
  337.         StripPathname(t);
  338.         StripExtension(t);
  339.         if t = '' then t := 'N/A';
  340.         Owner := StUpcase(t);
  341.       end;
  342.  
  343.     end {owner} ;
  344.  
  345.     procedure InitVectorTable;
  346.       {-build real equivalent of vector addresses}
  347.     var
  348.       v : VectorType;
  349.  
  350.       function RealAdd(a : Address) : Real;
  351.         {-return the real equivalent of an address (pointer)}
  352.       begin
  353.         with a do
  354.           RealAdd := 16.0*Cardinal(segment)+Cardinal(offset);
  355.       end {realadd} ;
  356.  
  357.     begin
  358.       for v := 0 to MaxVector do
  359.         Vtable[v] := RealAdd(Vectors[v]);
  360.     end {initvectortable} ;
  361.  
  362.     procedure WriteVecs(start, stop, startcol, wrapcol : Integer);
  363.       {-Show either trapped or chained interrupt vectors}
  364.  
  365.       procedure WriteHooks(start, stop, startcol, wrapcol : Integer);
  366.         {-show the trapped interrupt vectors}
  367.       var
  368.         v : VectorType;
  369.         sadd, eadd : Real;
  370.         col : Integer;
  371.       begin
  372.         sadd := 16.0*Cardinal(start);
  373.         eadd := 16.0*Cardinal(stop);
  374.         col := startcol;
  375.         for v := 0 to MaxVector do
  376.           if (Vtable[v] >= sadd) and (Vtable[v] <= eadd) then begin
  377.             if col+3 > wrapcol then begin
  378.               {wrap to next line}
  379.               WriteLn;
  380.               Write('':Pred(startcol));
  381.               col := startcol;
  382.             end;
  383.             Write(Copy(Hex(v), 3, 2), ' ');
  384.             col := col+3;
  385.           end;
  386.       end {writehooks} ;
  387.  
  388.       procedure WriteChained(pspAdd, startcol, wrapcol : Integer);
  389.         {-Write Chained interrupts as determined from watch file}
  390.       var
  391.         b : BlockType;
  392.         l : AllStrings;
  393.         llen : Byte absolute l;
  394.         lpos, col : Integer;
  395.       begin
  396.         {Scan table to find psp}
  397.         for b := NumChains downto 0 do
  398.           with ChainedVecs[b] do
  399.             if psp = pspAdd then begin
  400.               {Write the vector string, wrapping if needed}
  401.               l := vecstr;
  402.               col := startcol;
  403.               while (llen > 0) do begin
  404.                 if (l[1] = ' ') and (col+3 > wrapcol) then begin
  405.                   {wrap to next line}
  406.                   WriteLn;
  407.                   Write('':Pred(startcol));
  408.                   col := startcol;
  409.                 end else begin
  410.                   Write(l[1]);
  411.                   col := Succ(col);
  412.                 end;
  413.                 Delete(l, 1, 1);
  414.               end;
  415.               {Write only once in case of multiple release/reinstall}
  416.               exit;
  417.             end;
  418.       end {WriteChained} ;
  419.  
  420.     begin
  421.       if start = stop then
  422.         Exit;
  423.       if usewatch then
  424.         WriteChained(start, startcol, wrapcol)
  425.       else
  426.         WriteHooks(start, stop, startcol, wrapcol);
  427.     end {WriteVecs} ;
  428.  
  429.     procedure SortByPSP(var Blocks : BlockArray; BlockNum : BlockType);
  430.       {-sort in order of ascending PSP}
  431.     var
  432.       i, j : BlockType;
  433.       temp : Block;
  434.     begin
  435.       for i := 1 to Pred(BlockNum) do
  436.         for j := BlockNum downto Succ(i) do
  437.           if Cardinal(Blocks[j].psp) < Cardinal(Blocks[Pred(j)].psp) then begin
  438.             temp := Blocks[j];
  439.             Blocks[j] := Blocks[Pred(j)];
  440.             Blocks[Pred(j)] := temp;
  441.           end;
  442.     end {SortByPSP} ;
  443.  
  444.     procedure SumTheBlocks(var Blocks : BlockArray;
  445.                            BlockNum : BlockType;
  446.                            var Sum : BlockArray;
  447.                            var SumNum : BlockType);
  448.       {-combine the blocks with equivalent PSPs}
  449.     var
  450.       prevPSP : Integer;
  451.       b : BlockType;
  452.     begin
  453.       SumNum := 0;
  454.       prevPSP := 0;
  455.       for b := 1 to BlockNum do begin
  456.         if Blocks[b].psp <> prevPSP then begin
  457.           SumNum := Succ(SumNum);
  458.           Sum[SumNum] := Blocks[b];
  459.           prevPSP := Blocks[b].psp;
  460.           if prevPSP = CSeg then
  461.             {don't include the environment as part of free block's length}
  462.             Sum[SumNum].len := 0;
  463.         end else
  464.           with Sum[SumNum] do begin
  465.             cnt := Succ(cnt);
  466.             len := len+Blocks[b].len;
  467.           end;
  468.         {get length of the block which owns the executable program}
  469.         {for checking vector trapping next}
  470.         if Succ(Blocks[b].mcb) = Blocks[b].psp then
  471.           Sum[SumNum].psplen := Blocks[b].len;
  472.       end;
  473.     end {sumblocks} ;
  474.  
  475.     procedure TransferTheBlocks(var Blocks : BlockArray;
  476.                                 BlockNum : BlockType;
  477.                                 var Sum : BlockArray;
  478.                                 var SumNum : BlockType);
  479.       {-fill in the Sum array with a little initialization}
  480.     var
  481.       b : BlockType;
  482.     begin
  483.       for b := 1 to BlockNum do begin
  484.         Sum[b] := Blocks[b];
  485.         with Sum[b] do begin
  486.           cnt := 1;
  487.           if (Succ(mcb) = psp) and (psp <> 0) then
  488.             psplen := len
  489.           else
  490.             psplen := 0;
  491.         end;
  492.       end;
  493.       SumNum := BlockNum;
  494.     end {transfertheblocks} ;
  495.  
  496.     function OpenHandles(psp : Integer) : Integer;
  497.       {-return the number of open handles owned by a process}
  498.     var
  499.       h, o : Integer;
  500.       b : Byte;
  501.     begin
  502.       h := 0;
  503.       if (psp <> 8) and (cline <> 'N/A') then
  504.         for o := 0 to 19 do begin
  505.           b := Mem[psp:$18+o];
  506.           if not(b in [$FF, 0..2]) then
  507.             h := Succ(h);
  508.         end;
  509.       OpenHandles := h;
  510.     end {openhandles} ;
  511.  
  512.     function CommandLine(psp : Integer) : Pathname;
  513.       {-return the command line of the PSP}
  514.     var
  515.       t, s : Pathname;
  516.       i : Byte;
  517.     begin
  518.       if (psp <> 8) then begin
  519.         Move(Mem[psp:$80], t, 65);
  520.         if t[0] > #64 then t[0] := #64;
  521.         s := t;
  522.         StripNonAscii(t);
  523.         if s <> t then
  524.           {command line has been written over}
  525.           t := 'N/A'
  526.         else
  527.           {strip leading blanks}
  528.           while (Length(t) > 0) and (t[1] = #32) do Delete(t, 1, 1);
  529.       end else
  530.         {psp=8 is a special block owned by DOS containing the CONFIG.SYS drivers}
  531.         t := '';
  532.       CommandLine := t;
  533.     end {commandline} ;
  534.  
  535.     function PrevBlock(b : BlockType; psp : Integer) : BlockType;
  536.       {-return highest block with number less than b having a PSP matching psp}
  537.       {-return 0 if none}
  538.     var
  539.       t : BlockType;
  540.       found : Boolean;
  541.     begin
  542.       found := False;
  543.       t := Pred(b);
  544.       while (t > 0) and not(found) do begin
  545.         found := (Sum[t].psp = psp);
  546.         if not(found) then t := Pred(t);
  547.       end;
  548.       PrevBlock := t;
  549.     end {prevblock} ;
  550.  
  551.   begin
  552.     Write('Allocated Memory Map - by TurboPower Software - Version ', Version);
  553.  
  554.     if verbose then begin
  555.       WriteLn('  (verbose)');
  556.       WriteLn;
  557.       Write(' PSP  MCB files bytes owner    command line  ');
  558.       if usewatch then
  559.         WriteLn(chainst)
  560.       else
  561.         WriteLn(hookst);
  562.       WriteLn('---- ---- ----- ----- -------- ------------- -----------------------------');
  563.     end else begin
  564.       WriteLn;
  565.       WriteLn;
  566.       Write(' PSP  blks bytes owner    command line        ');
  567.       if usewatch then
  568.         WriteLn(chainst)
  569.       else
  570.         WriteLn(hookst);
  571.       WriteLn('----- ---- ----- -------- ------------------- ------------------------------');
  572.     end;
  573.  
  574.     DOSv := DOSversion;
  575.     CommandPSP := Blocks[2].psp;
  576.     InitVectorTable;
  577.     if verbose then
  578.       TransferTheBlocks(Blocks, BlockNum, Sum, SumNum)
  579.     else begin
  580.       SortByPSP(Blocks, BlockNum);
  581.       SumTheBlocks(Blocks, BlockNum, Sum, SumNum);
  582.     end;
  583.  
  584.     for b := 1 to SumNum do with Sum[b] do begin
  585.  
  586.       {get the command line which invoked the program}
  587.       if b = SumNum then
  588.         cline := ''
  589.       else
  590.         cline := CommandLine(psp);
  591.  
  592.       {write out numerical information}
  593.       Write(Hex(psp), ' ');   {PSP address}
  594.       if verbose then begin
  595.         Write(Hex(mcb), '  ', {MCB address}
  596.         OpenHandles(psp):2, '  '); {number of open file handles}
  597.       end else
  598.         Write(cnt:3, '  ');   {number of blocks}
  599.  
  600.       Write(16.0*Cardinal(len):6:0, ' '); {size of block in bytes}
  601.  
  602.       {get the program owning this block by scanning the environment}
  603.       if psp = CSeg then
  604.         st := 'free'
  605.       else if psp = CommandPSP then
  606.         st := 'command'
  607.       else if psp = Sum[1].psp then
  608.         st := 'config'
  609.       else if (DOSv >= 3) then begin
  610.         if verbose then begin
  611.           if Succ(mcb) = env then
  612.             {this is the environment block}
  613.             st := Owner(env)
  614.           else if PrevBlock(b, psp) <> 0 then
  615.             {this is the block that goes with the environment}
  616.             st := Owner(Sum[PrevBlock(b, psp)].env)
  617.           else
  618.             st := 'N/A';
  619.         end else if cnt > 1 then
  620.           st := Owner(env)
  621.         else
  622.           st := 'N/A';
  623.       end else
  624.         st := 'N/A';
  625.       while Length(st) < 9 do st := st+' ';
  626.       Write(st);
  627.  
  628.       {write the command line that invoked the program}
  629.       if verbose then
  630.         StLen := 13
  631.       else
  632.         StLen := 19;
  633.       if Length(cline) > StLen-3 then
  634.         cline := Copy(cline, 1, StLen-3)+'...'
  635.       else
  636.         while Length(cline) < StLen do cline := cline+' ';
  637.       Write(cline, ' ');
  638.  
  639.       {write the trapped interrupt vectors}
  640.       if verbose then
  641.         WriteVecs(psp, psp+psplen, 46, 75)
  642.       else if (b <> SumNum) then
  643.         WriteVecs(psp, psp+psplen, 47, 75);
  644.  
  645.       WriteLn;
  646.     end;
  647.  
  648.   end {showtheblocks} ;
  649.  
  650.   procedure ShowTheEMSblocks;
  651.     {-map out expanded memory, if present}
  652.   const
  653.     EMSinterrupt = $67;       {the vector used by the expanded memory manager}
  654.     MaxHandles = 255;
  655.  
  656.   type
  657.     HandlePageRecord =
  658.     record
  659.       handle : Integer;
  660.       numpages : Integer;
  661.     end;
  662.  
  663.     PageArray = array[0..MaxHandles] of HandlePageRecord;
  664.     PageArrayPtr = ^PageArray;
  665.     Pathname = string[64];
  666.  
  667.   var
  668.     EMSregs : registers;
  669.     EMShandles : Integer;
  670.     Map : PageArrayPtr;
  671.     TotalPages : Integer;
  672.  
  673.     function EMSpresent : Boolean;
  674.       {-return true if EMS memory manager is present}
  675.     var
  676.       f : file;
  677.       present : Boolean;
  678.     begin
  679.       {"file handle" defined by the expanded memory manager at installation}
  680.       Assign(f, 'EMMXXXX0');
  681.       {$I-} Reset(f) {$I+} ;
  682.       present := (IOResult = 0);
  683.       if present then
  684.         Close(f);
  685.       EMSpresent := present;
  686.     end {EMSpresent} ;
  687.  
  688.     function EMSpagesAvailable(var TotalPages : Integer) : Integer;
  689.       {-return the number of 16K expanded memory pages available and unallocated}
  690.     begin
  691.       EMSregs.ah := $42;
  692.       Intr(EMSinterrupt, EMSregs);
  693.       if EMSregs.ah <> 0 then begin
  694.         WriteLn('EMS device not responding');
  695.         EMSpagesAvailable := 0;
  696.         Exit;
  697.       end;
  698.       EMSpagesAvailable := EMSregs.bx;
  699.       TotalPages := EMSregs.dx;
  700.     end {EMSpagesAvailable} ;
  701.  
  702.     function EMShandlesActive : Integer;
  703.       {-return the number of active EMS handles}
  704.     begin
  705.       EMSregs.ah := $4B;
  706.       Intr(EMSinterrupt, EMSregs);
  707.       if EMSregs.ah <> 0 then begin
  708.         WriteLn('EMS device not responding');
  709.         EMShandlesActive := 0;
  710.         Exit;
  711.       end;
  712.       EMShandlesActive := EMSregs.bx;
  713.     end {EMShandlesActive} ;
  714.  
  715.     procedure EMSpageMap(var PageMap : PageArray);
  716.       {-return an array of the allocated memory blocks}
  717.     begin
  718.       EMSregs.ah := $4D;
  719.       EMSregs.es := Seg(PageMap);
  720.       EMSregs.di := Ofs(PageMap);
  721.       EMSregs.bx := 0;
  722.       Intr(EMSinterrupt, EMSregs);
  723.       if EMSregs.ah <> 0 then
  724.         WriteLn('EMS device not responding');
  725.     end {EMSpageMap} ;
  726.  
  727.     procedure WriteEMSmap(PageMap : PageArray; handles : Integer);
  728.       {-write out the EMS page map}
  729.     var
  730.       h : Integer;
  731.     begin
  732.       WriteLn('block   bytes   (Expanded Memory)');
  733.       WriteLn('-----   ------');
  734.       for h := 0 to Pred(handles) do
  735.         WriteLn(h:5, '  ', (16384.0*Cardinal(PageMap[h].numpages)):7:0);
  736.     end {writeEMSmap} ;
  737.  
  738.   begin
  739.     if not(EMSpresent) then Exit;
  740.     EMShandles := EMShandlesActive;
  741.     WriteLn;
  742.     GetMem(Map, 4*EMShandles);
  743.     EMSpageMap(Map^);
  744.     WriteEMSmap(Map^, EMShandles);
  745.     WriteLn(' free  ', (16384.0*Cardinal(EMSpagesAvailable(TotalPages))):7:0);
  746.     WriteLn('total  ', (16384.0*Cardinal(TotalPages)):7:0);
  747.   end {showtheemsblocks} ;
  748.  
  749.   procedure GetOptions;
  750.     {-Analyze command line for options}
  751.   const
  752.     unknop : string[24] = 'Unknown command option: ';
  753.   var
  754.     arg : AllStrings;
  755.     arglen : Byte absolute arg;
  756.     i : Integer;
  757.     watchname : Pathname;
  758.  
  759.     procedure WriteHelp;
  760.       {-Show the options}
  761.     begin
  762.       WriteLn('MAPMEM ', Version, ', by TurboPower Software');
  763.       WriteLn('====================================================');
  764.       writeln;
  765.       writeln('MAPMEM produces a report showing what memory resident');
  766.       writeln('programs are installed, how much memory each uses, and');
  767.       writeln('what interrupt vectors are taken over.');
  768.       writeln;
  769.       WriteLn('MAPMEM accepts the following command line syntax:');
  770.       WriteLn;
  771.       WriteLn('  MAPMEM [Options]');
  772.       WriteLn;
  773.       WriteLn('Options may be preceded by either / or -. Valid options');
  774.       WriteLn('are as follows:');
  775.       WriteLn;
  776.       WriteLn('     /W [Watchfile]');
  777.       writeln('            Used in combination with WATCH.COM, this option');
  778.       writeln('            causes MAPMEM to show the vectors taken over when');
  779.       writeln('            each program went resident, not just those still');
  780.       writeln('            controlled when MAPMEM is run. If Watchfile is not');
  781.       writeln('            specified, the file \TSR.DAT is used.');
  782.       WriteLn('     /V     Verbose report.');
  783.       WriteLn('     /?     Write this help screen.');
  784.       Halt(1);
  785.     end {WriteHelp} ;
  786.  
  787.     procedure ReadWatchFile(wname : Pathname);
  788.       {-Read a watch file into a structured array}
  789.     var
  790.       l : AllStrings;
  791.       f : Text[4096];
  792.       b : BlockType;
  793.       code : Integer;
  794.  
  795.       procedure Parse(l : AllStrings; var psp : Integer; var vecstr : AllStrings);
  796.         {-Parse line l, returning its components}
  797.  
  798.         procedure StripBlanks(var l : AllStrings);
  799.           {-Remove leading blanks from l}
  800.         var
  801.           len : Byte absolute l;
  802.         begin
  803.           while (len > 0) and (l[1] = ' ') do
  804.             Delete(l, 1, 1);
  805.         end {StripBlanks} ;
  806.  
  807.         procedure GetNum(var l : AllStrings; var num : Integer);
  808.           {-Parse next hex integer from line}
  809.         const
  810.           hexset : set of Char = ['0'..'9', 'A'..'F'];
  811.         var
  812.           numstr : AllStrings;
  813.           len : Byte absolute l;
  814.         begin
  815.           StripBlanks(l);
  816.           numstr := '';
  817.           while (len > 0) and (UpCase(l[1]) in hexset) do begin
  818.             numstr := numstr+l[1];
  819.             Delete(l, 1, 1);
  820.           end;
  821.           num := 0;
  822.           Val('$'+numstr, num, code);
  823.           if code <> 0 then
  824.             Abort('Illegal entry in watch file');
  825.         end {GetNum} ;
  826.  
  827.         procedure GetVec(var l,vecstr:allstrings);
  828.           {-Extract the vector list from l}
  829.         const
  830.           hexset : set of Char = ['0'..'9', 'A'..'F'];
  831.         var
  832.           len:byte absolute l;
  833.         begin
  834.           {line is of form 08|F000:2DF5 09:F000:2034}
  835.           vecstr:='';
  836.           while (len>0) do begin
  837.             stripblanks(l);
  838.             {Get next vector}
  839.             if vecstr<>'' then
  840.               vecstr:=vecstr+' ';
  841.             while (len>0) and (l[1] in hexset) do begin
  842.               vecstr:=vecstr+l[1];
  843.               delete(l,1,1);
  844.             end;
  845.             {Delete extraneous info}
  846.             while (len>0) and (l[1]<>' ') do
  847.               delete(l,1,1);
  848.           end;
  849.         end {GetVec} ;
  850.  
  851.       begin
  852.         {Get the psp}
  853.         GetNum(l, psp);
  854.         {Get the vector list}
  855.         GetVec(l, vecstr);
  856.       end {Parse} ;
  857.  
  858.     begin
  859.       {$I-}
  860.       Assign(f, wname);
  861.       Reset(f);
  862.       if IOResult <> 0 then
  863.         Abort('Watch file '+wname+' not found....');
  864.       b := 0;
  865.       while not(EoF(f)) do begin
  866.         ReadLn(f, l);
  867.         if IOResult <> 0 then
  868.           Abort('Error reading watch file '+wname+'....');
  869.         with ChainedVecs[b] do
  870.           Parse(l, psp, vecstr);
  871.         b := Succ(b);
  872.       end;
  873.       NumChains := Pred(b);
  874.       Close(f);
  875.       {$I+}
  876.     end {ReadWatchFile} ;
  877.  
  878.   begin
  879.  
  880.     WriteLn;
  881.     {Initialize defaults}
  882.     verbose := False;
  883.     usewatch := False;
  884.     watchname := '\TSR.DAT';
  885.  
  886.     i := 1;
  887.     while i <= ParamCount do begin
  888.       arg := ParamStr(i);
  889.       if (arg[1] = '?') then
  890.         WriteHelp
  891.       else if (arg[1] = '-') or (arg[1] = '/') then
  892.         case arglen of
  893.           1 : Abort('Missing command option following '+arg);
  894.           2 : case UpCase(arg[2]) of
  895.                 '?' : WriteHelp;
  896.                 'V' : verbose := True;
  897.                 'W' : begin
  898.                         usewatch := True;
  899.                         if (i < ParamCount) then begin
  900.                           arg := ParamStr(Succ(i));
  901.                           if (arg[1] <> '-') and (arg[1] <> '/') then begin
  902.                             i := Succ(i);
  903.                             watchname := ParamStr(i);
  904.                           end;
  905.                         end;
  906.                       end;
  907.               else
  908.                 Abort(unknop+arg);
  909.               end;
  910.         else
  911.           Abort(unknop+arg);
  912.         end
  913.       else
  914.         Abort(unknop+arg);
  915.       i := Succ(i);
  916.     end;
  917.  
  918.     {Read Watch data file if requested}
  919.     if usewatch then
  920.       ReadWatchFile(watchname);
  921.  
  922.   end {GetOptions} ;
  923.  
  924. begin {MapMem}
  925.   GetOptions;
  926.   FindTheBlocks;
  927.   ShowTheBlocks;
  928.   ShowTheEMSblocks;
  929. end.  {MapMem}
  930.